// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package syntax

import "fmt"

// PosMax是可以不丢失地表示的最大行或列值。
// 大于PosMax的传入值（参数）将设置为PosMax。
const PosMax = 1 << 30

// Pos表示绝对（线、列）震源位置
// 参照位置基准计算相对误差
// （到文件或行指令）位置信息。
// Pos值故意为轻量级，以便
// 可以在不太担心空间使用的情况下创建。
type Pos struct {
	base      *PosBase
	line, col uint32
}

// MakePos返回给定PosBase、line和column的新Pos。
func MakePos(base *PosBase, line, col uint) Pos { return Pos{base, sat32(line), sat32(col)} }

// 已知TODO（gri）假设基线小于1。
// 也许我们应该检查一下Base（）！=而不是零。

func (pos Pos) Pos() Pos       { return pos }
func (pos Pos) IsKnown() bool  { return pos.line > 0 }
func (pos Pos) Base() *PosBase { return pos.base }
func (pos Pos) Line() uint     { return uint(pos.line) }
func (pos Pos) Col() uint      { return uint(pos.col) }

func (pos Pos) RelFilename() string { return pos.base.Filename() }

func (pos Pos) RelLine() uint {
	b := pos.base
	if b.Line() == 0 {
		// 基线未知=>相对线未知
		return 0
	}
	return b.Line() + (pos.Line() - b.Pos().Line())
}

func (pos Pos) RelCol() uint {
	b := pos.base
	if b.Col() == 0 {
		// 基列未知=>相对列未知
		// （行指令的当前规范要求
		// 这将一直应用到下一个PosBase/line指令，
		// 不只是在新的换行之前）
		return 0
	}
	if pos.Line() == b.Pos().Line() {
		// pos与pos base在同一行=>列相对于pos base
		return b.Col() + (pos.Col() - b.Pos().Col())
	}
	return pos.Col()
}

// Cmp比较位置p和q，并返回结果r，如下所示：
// None
// r<0:p在q之前
// r==0:p和q是相同的位置（但可能不相同）
// r>0:p在q之后
// None
// 如果p和q在不同的文件中，如果文件名为
// of p在q的文件名之前按字典顺序排序。
func (p Pos) Cmp(q Pos) int {
	pname := p.RelFilename()
	qname := q.RelFilename()
	switch {
	case pname < qname:
		return -1
	case pname > qname:
		return +1
	}

	pline := p.Line()
	qline := q.Line()
	switch {
	case pline < qline:
		return -1
	case pline > qline:
		return +1
	}

	pcol := p.Col()
	qcol := q.Col()
	switch {
	case pcol < qcol:
		return -1
	case pcol > qcol:
		return +1
	}

	return 0
}

func (pos Pos) String() string {
	rel := position_{pos.RelFilename(), pos.RelLine(), pos.RelCol()}
	abs := position_{pos.Base().Pos().RelFilename(), pos.Line(), pos.Col()}
	s := rel.String()
	if rel != abs {
		s += "[" + abs.String() + "]"
	}
	return s
}

// TODO（gri）清理：找到更好的名称，避免与错误_test.go中的位置冲突
type position_ struct {
	filename  string
	line, col uint
}

func (p position_) String() string {
	if p.line == 0 {
		if p.filename == "" {
			return "<unknown position>"
		}
		return p.filename
	}
	if p.col == 0 {
		return fmt.Sprintf("%s:%d", p.filename, p.line)
	}
	return fmt.Sprintf("%s:%d:%d", p.filename, p.line, p.col)
}

// PosBase表示相对位置信息的基础：
// 在位置pos处，相对位置为filename:line:col。
type PosBase struct {
	pos       Pos
	filename  string
	line, col uint32
}

// NewFileBase为给定文件名返回一个新的PosBase。
// 文件PosBase的位置相对于其自身，具有
// 文件名的位置：1:1。
func NewFileBase(filename string) *PosBase {
	base := &PosBase{MakePos(nil, linebase, colbase), filename, linebase, colbase}
	base.pos.base = base
	return base
}

// NewLineBase为行指令“line filename:line:col”返回一个新的PosBase
// 相对于位置，即紧跟其后的字符的位置
// 包含行指令的注释。对于行注释中的指令，
// 该位置是下一行（即换行符）的开始
// 属于行注释）。
func NewLineBase(pos Pos, filename string, line, col uint) *PosBase {
	return &PosBase{pos, filename, sat32(line), sat32(col)}
}

func (base *PosBase) IsFileBase() bool {
	if base == nil {
		return false
	}
	return base.pos.base == base
}

func (base *PosBase) Pos() (_ Pos) {
	if base == nil {
		return
	}
	return base.pos
}

func (base *PosBase) Filename() string {
	if base == nil {
		return ""
	}
	return base.filename
}

func (base *PosBase) Line() uint {
	if base == nil {
		return 0
	}
	return uint(base.line)
}

func (base *PosBase) Col() uint {
	if base == nil {
		return 0
	}
	return uint(base.col)
}

func sat32(x uint) uint32 {
	if x > PosMax {
		return PosMax
	}
	return uint32(x)
}
